package com.ericsson.sds.samples.b2bua;
/* **********************************************************************
 * Copyright (c) Ericsson 2007. All Rights Reserved.
 * Reproduction in whole or in part is prohibited without the 
 * written consent of the copyright owner. 
 * 
 * ERICSSON MAKES NO REPRESENTATIONS OR WARRANTIES ABOUT THE SUITABILITY 
 * OF THE SOFTWARE, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED 
 * TO THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR 
 * PURPOSE, OR NON-INFRINGEMENT. ERICSSON SHALL NOT BE LIABLE FOR ANY 
 * DAMAGES SUFFERED BY LICENSEE AS A RESULT OF USING, MODIFYING OR 
 * DISTRIBUTING THIS SOFTWARE OR ITS DERIVATIVES. 
 * 
 * **********************************************************************/


import java.io.IOException;
import java.util.HashMap;
import java.util.Map;

import javax.servlet.ServletConfig;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.sip.Address;
import javax.servlet.sip.SipApplicationSession;
import javax.servlet.sip.SipFactory;
import javax.servlet.sip.SipServlet;
import javax.servlet.sip.SipServletMessage;
import javax.servlet.sip.SipServletRequest;
import javax.servlet.sip.SipServletResponse;
import javax.servlet.sip.SipSession;
import javax.servlet.sip.SipURI;

/**
 * This shows how a back to back user agent can be used. 
 * This servlet receives an INVITE from a user and establishes a session with him. 
 * When users send MESSAGEs in the dialog, the servlet recreates 
 * the MESSAGE in the corresponding dialog.
 */
public class B2BUAServletJSR116 extends SipServlet
{
    /**
     * 
     */
    private static final long serialVersionUID = 5274710663068941773L;

    /**
     * Name of the p-asserted-identity header. 
     */
    private static final String ASSERTED_IDENTITY_HEADER = "p-asserted-identity";
    
    /**
     * When a servlet keeps information about sessions, it should implement a SessionListener to know when the session is being invalidated.
     * The sip container can invalidate a session at any time. 
     * However for this sample application we will assume that sessions are always valid.
     */
    private Map<SipSession, SipSession> sessionMap = new HashMap<SipSession, SipSession>();
    
    /**
     * The SIP Factory. Can be used to create URI and requests.
     */
    private SipFactory sipFactory;

    /**
     * @inheritDoc
     */
    public void init(ServletConfig config) throws ServletException
    {
        super.init(config);
        ServletContext context = config.getServletContext();
        sipFactory = (SipFactory) context.getAttribute("javax.servlet.sip.SipFactory");
    }
    
    /**
     * The INVITE is used to initiate a dialog between two users. The servlet will intercept
     * all subssequent MESSAGEs in the dialog and send them to the other side. Since the 
     * servlet is acting as a B2BUA, the requests are received and handled (200 OK) before being 
     * re-created in the corresponding dialog.
     */
    protected void doInvite(SipServletRequest sipServletRequest) throws ServletException, IOException
    {
        // Send back 200 OK to accept the session
        SipServletResponse response = sipServletRequest.createResponse(200);
        response.send();
        
        // Create the new INVITE for the destinator
        Address from = sipServletRequest.getFrom();
        Address to = sipServletRequest.getTo();
        SipApplicationSession applicationSession = sipFactory.createApplicationSession();
        SipServletRequest request = sipFactory.createRequest(applicationSession, "INVITE", from, to);
        
        // The route is required because we want the request to go directory to the s-cscf.
        addCscfRoute(request);
        
        // We also need to add the p-asserted-identity header for the s-cscf to validate our request
        String assertedIdentity = sipServletRequest.getHeader(ASSERTED_IDENTITY_HEADER);
        addAssertedIdentity(request, assertedIdentity);
        
        // Set the default handler for the session. The default handler is the name of the servlet
        // as described in the sip.xml
        request.getSession().setHandler("B2BUAServletJSR116");
        
        // Send the request
        request.send();
        
        // Put the two entries in the map : one to match the callee with the caller and vice-versa. These entries will then be used to forward messages.
        // More code should handle the error cases (eg if the INVITE is refused) but this is not done here.
        SipSession calleeSession = request.getSession();
        SipSession callerSession = sipServletRequest.getSession();
        sessionMap.put(calleeSession, callerSession);
        sessionMap.put(callerSession, calleeSession);
    }
    
    /**
     * Adds the p-asserted-identity header to <code>request</code>. The content of the header is 
     * determiend by <code>assertedIdentity</code>. 
     * @param request the request to which the p-asserted-identity is added.
     * @param assertedIdentity the content of the header
     */
    private void addAssertedIdentity(SipServletRequest request, String assertedIdentity)
    {
        request.setHeader(ASSERTED_IDENTITY_HEADER, assertedIdentity);
    }

    /**
     * Adds a route to the request. This will force the request to be routed to the s-cscf
     * which is located by default on port 5082 on the localhost. This value needs to 
     * be changed if the s-cscf is located elsewhere.
     * @param request request to be updated with the new route.
     */
    private void addCscfRoute(SipServletRequest request)
    {
        SipURI route = sipFactory.createSipURI("", "127.0.0.1:5082");
        request.pushRoute(route);
    }

    @Override
    protected void doSuccessResponse(SipServletResponse resp) throws ServletException, IOException
    {
        // when a 200 OK for an INVITE is received, send an ACK
        if(hasCorrespondingSession(resp) && "INVITE".equals(resp.getMethod()))
        {
            SipServletRequest ack = resp.createAck();
            ack.send();
        }
    }

    /**
     * Returns <code>true</code> if the message has a session. 
     * @param resp
     * @return
     */
    private boolean hasCorrespondingSession(SipServletMessage message)
    {
        return sessionMap.get(message.getSession()) != null;
    }

    /**
     * When a MESSAGE request is received, the servlet checks if it is part of a session already handled. If so, 
     * it tries to find the matching session and sends a new MESSAGE to the destinator.
     */
    protected void doMessage(SipServletRequest req) throws ServletException, IOException
    {
        // Send back 200 ok
        SipServletResponse response = req.createResponse(200);
        response.send();
        
        if(hasCorrespondingSession(req))
        {
            // Create the new request in the corresponding dialog
            SipSession session = req.getSession();
            SipSession correspondingSession = sessionMap.get(session);
            SipServletRequest forwardedRequest = correspondingSession.createRequest(req.getMethod());

            // Add asserted identity
            String assertedIdentity = "<" + forwardedRequest.getFrom().getURI().toString() + ">";
            addAssertedIdentity(forwardedRequest, assertedIdentity);
            
            // Copy the content of the message
            forwardedRequest.setContent(req.getContent(), req.getContentType());
            
            forwardedRequest.send();
        }
    }
}
